/* C.ProfileT: Execution profiler (by time)
 *
 * Version 1.00, 04-06-1990
 *
 * Copyright (C) Ferdinand Oeinck 1990
 *
 * Modified, Paul Moore 02/06/91.
 *   To conform to the conventions of my "Utils" library.
 */

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include "swis.h"
#include "kernel.h"

#include "ProfileAsm.h"
#include "Profile.h"

static int *funcadr_ptr;
static int *count_ptr;
static int funccount;

static char *_profile_filename;
static void _profile_exithandler(void);

void profile_init_time (const char *filename)
{
	unsigned int *codestart;
	unsigned int *codeend;
	unsigned int *i;
	int fpestart, fpeend;
	int len;
	int instr;
	int count = 0;
	char *modulename;
	_kernel_swi_regs regs;

	if (_kernel_hostos() != _kernel_ARTHUR)
	{
		printf("Error: Not running on ARTHUR host OS\n");
		exit(1);
	}

	if (!_kernel_fpavailable())
	{
		printf("Warning: Execution time profiling not possible.\n");
		printf("         No floating point support.\n");
		return;
	}

	regs.r[0] = 12;
	regs.r[1] = 0;
	regs.r[2] = 0;

	while (_kernel_swi(OS_Module, &regs, &regs) == NULL)
	{
		modulename = (char*)(regs.r[3] + *((int *)(regs.r[3] + 0x10)));
		if (strcmp(modulename,"FPEmulator") == 0)
			break;
	}

	fpestart = regs.r[3];
	_kernel_swi(OS_Module, &regs, &regs);
	fpeend = regs.r[3];

	if (filename == NULL || *filename == '\0')
		_profile_filename = NULL;
	else
	{
		if ((_profile_filename = malloc(strlen(filename) + 1)) == NULL)
		{
			printf("Warning: Execution time profiling not possible.\n");
			printf("         Malloc failed.\n");
			return;
		}

		strcpy(_profile_filename, filename);
	}

	codestart = _profile_getstart();
	codeend = _profile_getend();

	for (i = codestart; i != codeend; i++)
	{
		instr = *i & 0xffff0000;
		if (instr == (unsigned int) 0xff000000)
		{
			len = *i & 0xffff;
			if (len >= strlen((char *)i - len))
				count++;
		}
	}
	count += 4;

	if ((funcadr_ptr = calloc(count, sizeof(int))) == NULL)
	{
		printf("Warning: Execution time profiling not possible.\n");
		printf("         Malloc failed.\n");
		free(_profile_filename);
		return;
	}

	if ((count_ptr = calloc(count, sizeof(int))) == NULL)
	{
		printf("Warning: Execution time profiling not possible.\n");
		printf("         Malloc failed.\n");
		free(funcadr_ptr);
		free(_profile_filename);
		return;
	}

	funccount = count;

	count = 1;
	for (i = codestart; i != codeend; i++)
	{
		instr = *i & 0xffff0000;
		if (instr == (unsigned int) 0xff000000)
		{
			len = *i & 0xffff;
			if (len >= strlen((char *)i - len))
			{
				funcadr_ptr[count++] = (int) i - len;
			}
		}
	}

	funcadr_ptr[count]     = (int) codeend;
	funcadr_ptr[count + 1] = (int) fpestart;
	funcadr_ptr[count + 2] = (int) fpeend;

	if (!_profile_ClaimIntDeviceVector())
	{
		free(count_ptr);
		free(funcadr_ptr);
		free(_profile_filename);
		printf("Warning: Execution time profiling not possible.\n");
		printf("         I think this is not RISC_OS 2.00\n");
		return;
	}

	atexit(_profile_exithandler);
}

#define HEADER		\
" Num   Address  Function name                         Percentage    Count\n"

#define UNDERLINE	\
"=========================================================================\n"

#define FORMAT		\
"%4i  %8x  %-40s  %6.2f  %7i\n"

#define TOTALS		\
"%57s %6.2f  %7i\n", ""

static void _profile_exithandler(void)
{
	int i;
	int sumc = 0;
	float sum = 0;
	float sump = 0;
	FILE *fp;

	_profile_ReleaseIntDeviceVector();

	for (i = 0; i < funccount; i++)
		sum += count_ptr[i];

	/* Subtract double counted fp values */
	sum -= count_ptr[funccount - 2];

	if (sum <= 0)
		sum = 1;

	if (_profile_filename == NULL)
	{
		fp = stderr;
	}
	else if ((fp = fopen(_profile_filename, "w")) == NULL)
	{
		fprintf(stderr,"Can't write profile data to %s - using stderr\n",_profile_filename);
		fp = stderr;
	}

	fprintf(fp, HEADER);
	fprintf(fp, UNDERLINE);

	fprintf(fp, FORMAT,
		0,
		funcadr_ptr[1],
		"Before user code (kernel)",
		100.0 * (float) count_ptr[0] / sum,
		count_ptr[0]);

	sump += (float) 100.0 * (float) count_ptr[0] / sum;
	sumc += count_ptr[0];

	for (i = 1 ; i < funccount - 3; i++)
	{
		fprintf(fp, FORMAT,
			i,
			funcadr_ptr[i],
			(char *) funcadr_ptr[i],
			100.0 * (float) count_ptr[i] / sum,
			count_ptr[i]);

		sump += (float) 100.0 * (float) count_ptr[i] / sum;
		sumc += count_ptr[i];
	}

	fprintf(fp, FORMAT,
		funccount-3,
		funcadr_ptr[funccount-3],
		"After user code (kernel)",
		100.0 * (float) count_ptr[funccount-3] / sum,
		count_ptr[funccount-3]);

	sump += (float) 100.0 * (float) count_ptr[funccount-3] / sum;
	sumc += count_ptr[funccount - 3];

	fprintf(fp, UNDERLINE);
	fprintf(fp, TOTALS, sump, sumc);
	fprintf(fp, "\nPercentage of time spent in the FPE\n");
	fprintf(fp, FORMAT,
		funccount-2,
		funcadr_ptr[funccount-2],
		"fpemulator",
		100.0 * (float) count_ptr[funccount-2] / sum,
		count_ptr[funccount-2]);

	if (fp != stderr)
		fclose(fp);

	free(count_ptr);
	free(funcadr_ptr);
	free(_profile_filename);
}

/* Binary search of address table.
 * This function is called every 1/100 second
 */

#define Between(x,l,h) ((int)l < (int)x && (int)x <= (int)h)

void binsearch (int *adr)
{
	int high = funccount - 1;
	int low = 1;

	if ((int) adr < (int) funcadr_ptr[1])
	{
		++count_ptr[0];
		return;
	}

	if (Between(adr,funcadr_ptr[funccount-2],funcadr_ptr[funccount-1]))
	{
		/* We are in the FPE, so use that return address */
        	++count_ptr[funccount-2];
		adr = (int *)_profile_fp_return_address;
	}

	if ((int) adr > (int) funcadr_ptr[funccount-3])
	{
		++count_ptr[funccount-3];
		return;
	}

	while ((high - low) > 1)
	{
        	int middle = (high + low) / 2;
		if (Between(adr,funcadr_ptr[middle],funcadr_ptr[middle + 1]))
		{
			low = middle;
			break;
		}

		if ((int)adr > (int)funcadr_ptr[middle + 1])
			low = middle + 1;
		else
			high = middle;
	}

	++count_ptr[low];
}
